今天的要來介紹 Self Relations ,第一次聽到這名詞的你可能會覺得很酷, relation 除了一對一、一對多、多對多外,自己原來也可以 relation 自己,會有 Self Relations 的出現原因是,我們的業務需求中,對於 model 的使用上,會有同一個 model 但是不同解讀的方式,舉個例子以 teacher 跟 student 來說,他們的 model 都是屬於 User ,同時學生可能會有他的老師,相反老師可能也會有自己的學生,所以這個情形就很適合用 Self Relations 去描敘關聯的關係,Self Relations 的結構上,很像是一個上下層的概念去 chain 在一起, 很特別的是他可以用在 1-1、1-n、m-n 中,這邊我們將慢慢介紹~
這邊就是一個單純的一對一的關係,你會看到:
user 可能會有 0 個或一個前輩user 可能會有 0 個或一個繼任者這邊要注意的是,如果是 1-1 的 Self-Relations 兩邊的 relation 不能都是 required 因為這樣你會無法 create 第一筆 User ,1-1 必需要先有 user list 後,你才能用類似於 linklist 的方式,去指定該 User 的上下關係,並 connect 這位 User 的前輩是誰繼任者是誰
model User {
id Int @id @default(autoincrement())
name String?
successorId Int? @unique
successor User? @relation("BlogOwnerHistory", fields: [successorId], references: [id])
predecessor User? @relation("BlogOwnerHistory")
}
這邊有幾個小規則:
relation 必須要有相同的 name 例如這邊的 BlogOwnerHistory
relation 要完整描述,relation 的全部內容1-1 的寫法一樣,其中一邊要當作 foreign key 去關聯到哪個 User 這邊來說就是 successorId
同時被定義成 foreign key 的欄位,這邊來說就是 successorId ,必須要是 unique 的以確保 1-1 的唯一性
model User {
id Int @id @default(autoincrement())
name String?
successorId Int? @unique
successor User? @relation("BlogOwnerHistory", fields: [successorId], references: [id])
predecessor User? @relation("BlogOwnerHistory")
}
那因為是 1-1 所以你要反過來讓 predecessorId 當作 foreign key 也是 OK 一樣你需要確保 predecessorId 的 unique
model User {
id Int @id @default(autoincrement())
name String?
successor User? @relation("BlogOwnerHistory")
predecessorId Int? @unique
predecessor User? @relation("BlogOwnerHistory", fields: [predecessorId], references: [id])
}
那不管你是 predecessorId 或是 successorId 當你的 foreign key ,在 prisma client 寫法都是一樣的~
const x = await prisma.user.create({
data: {
name: "Bob McBob",
successor: {
connect: {
id: 2,
},
},
predecessor: {
connect: {
id: 4,
},
},
},
});
以下是 SQL 對於 1-1 Self Relation 的語法,跟 1-1 的 SQL 一樣的是需要對 successorId 加上 Unique 的 CONSTRAINT
CREATE TABLE "User" (
id SERIAL PRIMARY KEY,
"name" TEXT,
"successorId" INTEGER
);
ALTER TABLE "User" ADD CONSTRAINT fk_successor_user FOREIGN KEY ("successorId") REFERENCES "User" (id);
ALTER TABLE "User" ADD CONSTRAINT successor_unique UNIQUE ("successorId");
這邊的 1-n 的關係會是
student 可能會有 0 個或一個 teacher
teacher 可能會有 0 個或多個 student
model User {
id Int @id @default(autoincrement())
name String?
teacherId Int?
teacher User? @relation("TeacherStudents", fields: [teacherId], references: [id])
students User[] @relation("TeacherStudents")
}
那跟 1-n 的 SQL 一樣,不需要在 teacherId 加上 unique ,因為多個學生,可能會給同一個 teacher 教到~
CREATE TABLE "User" (
id SERIAL PRIMARY KEY,
"name" TEXT,
"teacherId" INTEGER
);
ALTER TABLE "User" ADD CONSTRAINT fk_teacherid_user FOREIGN KEY ("teacherId") REFERENCES "User" (id);
這邊的 m-n 的關係會是
user 可以被 followed 0 到多個 user
user 可能會去 follow 0 到多個 user
這邊跟 m-n 的寫法一樣都適用 implicit ,prisma 自動幫你管理中間表的部分
model User {
id Int @id @default(autoincrement())
name String?
followedBys User[] @relation("UserFollows")
followings User[] @relation("UserFollows")
}
那如果需要管理中間表的話可以用 explicit mode~
model User {
id Int @id @default(autoincrement())
name String?
followedBys Follows[] @relation("followedBy")
followings Follows[] @relation("following")
}
model Follows {
followedBy User @relation("followedBy", fields: [followedById], references: [id])
followedById Int
following User @relation("following", fields: [followingId], references: [id])
followingId Int
@@id([followingId, followedById])
}
對應的 SQL 語法如下
CREATE TABLE "User" (
id integer DEFAULT nextval('"User_id_seq"'::regclass) PRIMARY KEY,
name text
);
CREATE TABLE "_UserFollows" (
"A" integer NOT NULL REFERENCES "User"(id) ON DELETE CASCADE ON UPDATE CASCADE,
"B" integer NOT NULL REFERENCES "User"(id) ON DELETE CASCADE ON UPDATE CASCADE
);
在 prisma client 的寫法如下~你會發現不管是 implicit 或是 explicit 都跟之前的寫法一樣,只是變成兩個欄位 followedBys 跟 followings 去 connect
//implicit
const newUser = await prismaClient.user.create({
data: {
name: "Danny3.0",
followedBys: {
connect: {
id: 1
}
},
followings: {
connect: [
{ id: 5 }, { id: 6 }, { id: 7 }
]
}
}
})
//explicit
const newUser = await prismaClient.user.create({
data: {
name: "Danny3.0",
followedBys: {
create: [{
followedBy: {
connect: {
id: 1
}
}
}]
},
followings: {
createMany: {
data: [
{ followingId: 5 },
{ followingId: 6 },
{ followingId: 7 },
]
}
}
}
})
Self Relation 很特別的是可以同時寫 1-1、1-n、m-n 情況在同一個 model 中
model User {
id Int @id @default(autoincrement())
name String?
teacherId Int?
teacher User? @relation("TeacherStudents", fields: [teacherId], references: [id])
students User[] @relation("TeacherStudents")
followedBy User[] @relation("UserFollows")
following User[] @relation("UserFollows")
}
✅ 前端社群 :
https://lihi3.cc/kBe0Y